# CDN.pm
# Copyright (C) 2006, 2007  Stephane Alnet
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# 

#
# For more information visit http://carrierclass.net/
#
package CCNCore::Provision::CDN;
use strict;
use warnings;
use base qw(CCNCore::Provision::Base);

=pod

    CCNCore::Provision::CDN

    Provides downstream (GUI -> CDN servers -> edge servers) distribution of
    content. The (GUI -> CDN servers) section is referred to as the
    "CDN downstream high" section, while the (CDN servers -> edge servers)
    section is referred to as the "CDN downstream low" section.

    CDN downstream currently uses rsync over ssh. A substantially better
    approach would be to use BitTorrent or a similar peer-to-peer system,
    which would eliminate the need for the CDN servers; however the
    authentication/integrity issues would have to be addressed first
    (CDN downstream is especially used to transfer the configuration
    packages from the GUI nodes to the edge servers).

    One particularity of the "high" section (this also applies to the "upstream"
    section) is that it is accross "systems" (as defined in system.sql), more
    specifically between the system that hosts the GUI (as defined in the
    "provisioning" SQL table) and the target system (one of many).

    Note: The cdn_upstream_(high|low) modules provide the reverse path
    (content aggregation).

=cut

use CCNCore::Home;
use CCNCore::Scripting;
use CCNCore::IPC::Spool;

sub run()
{
    my $self = shift;

    # All nodes need rsync
    $self->PACKAGE('rsync');
}

sub distribute_high
{
    my $self = shift;
    my $t1 = shift;
    my $target_pk = pop; # Optional: target server pk
    
    return unless $self->p->is_a_gui_node;

    # CDN downstream "high"
    # Push the content down to the CDN "downstream" servers

    $t1->set_label('CDN downstream '.$self->what().' (high)');

    my $count = 0;

    my $where = '';
    if(defined $target_pk)
    {
        $where = "WHERE id = $target_pk";
    }
    
    my $spool = new CCNCore::IPC::Spool;

    $self->sql_do_for(<<"SQL",
        SELECT DISTINCT cdn_downstream_server FROM server $where
SQL
    sub
    {
        my $cdn_server = shift;
        my @files = $self->cdn_files($cdn_server);

        my $target = $self->primary_ip($cdn_server);

        # On the GUI server, the files are stored relative to
        # /home/www/data/
        # XXX-URGENT they should be in "shared" or something similar.
        # On the CDN servers, they are stored relative to
        # /home/config/local/loads/
        # However cdn_files() may specify specific pairs of 
        # absolute paths as ARRAYREFs.

        for my $ref (@files)
        {
            my $src = CCNCore::Home::www."/data/$ref";
            my $dst = CCNCore::Home::config."/local/loads/$ref";
            if(ref($ref) eq 'ARRAY')
            {
                $src = $ref->[0];
                $dst = $ref->[1];
            }
            $spool->spool_request("rsync ".$t1->ticket()." $src root\@${target}:$dst");
            $count++;
        }

    });

    $t1->add_action($count,'cdn-downstream-high-completed '.$self->what().(defined($target_pk)?" $target_pk":''));
    return $count;
}

sub distribute_low
{
    my $self = shift;
    my $t2 = shift;
    my $target_pk = pop; # Optional: target server pk

    return unless $self->p->is_a_cdn_downstream_node;

    # CDN downstream "low"
    # Push the content down to the edge servers
    # Note: the content has already been downloaded at the right location 
    #       onto the CDN server, so there is no need to re-synchronize it.

    $t2->set_label('CDN downstream '.$self->what().' (low)');

    my $count = 0;

    my $and = '';
    if(defined $target_pk)
    {
        $and = " AND id = $target_pk"; 
    }
    
    my $spool = new CCNCore::IPC::Spool;

    $self->sql_do_for(<<"SQL",$self->p->server_pk,$self->p->server_pk,
        SELECT id FROM server WHERE cdn_downstream_server = ? AND id <> ? $and
SQL
    sub
    {
        my $server = shift;
        my @files = $self->edge_files($server);

        my $target = $self->primary_ip($server);

        # The files are normally stored relative to
        # /home/config/local/loads/
        # However the scripts may specify absolute paths instead.

        for my $ref (@files)
        {
            my $src = CCNCore::Home::config."/local/loads/$ref";
            my $dst = $src;
            if( $ref =~ m{^/} )
            {
                $src = $dst = $ref;
            }
            $spool->spool_request("rsync ".$t2->ticket()." $src root\@${target}:$dst");
            $count++;
        }

    });

=pod

    'wait' is ran on the CDN node that requested the distribution
    once all the rsync operations to the edge nodes have been completed.

    Our ticket contains information about the parent (GUI Node)'s ticket;
    the information was put there by cdn_downstream_low_configuration.pm
    (or others).

=cut

    $t2->add_action($count,'wait');
    return $count;
}

=pod
    cdn_files($cdn_server)

    Return the list of files to be sent to the cdn_server (a server.pk)
    for this specific class of files.
=cut

sub cdn_files($)
{
    my $self = shift;
    $self->die("Internal Error: Class ".ref($self)." does not provide cdn_files()");
}

=pod
    edge_files($edge_server)

    Return the list of files to be sent to the edge_server (a server.pk)
    for this specific class of files.
=cut

sub edge_files($)
{
    my $self = shift;
    $self->die("Internal Error: Class ".ref($self)." does not provide edge_files()");
}

=pod
    what()
    
    Return a name used for the completed actions to be triggered.
    
=cut

sub what()
{
    my $self = shift;
    return 'dont-know-what';
}

1;